using BepuPhysics;
using BepuUtilities.Memory;
using DemoContentLoader;
using DemoRenderer;
using DemoRenderer.UI;
using DemoUtilities;
using System;

namespace Demos
{
    public abstract class Demo : IDisposable
    {
        /// <summary>
        /// 获取由演示的Initialize调用创建的模拟。
        /// </summary>
        public Simulation Simulation { get; protected set; }

        // 请注意,模拟使用的缓冲池不会被视为模拟*拥有*。模拟仅使用池。
        // 处置模拟不会处置或清除缓冲池。
        /// <summary>
        /// 获取演示模拟使用的缓冲池。
        /// </summary>
        public BufferPool BufferPool { get; private set; }

        /// <summary>
        /// 获取可供模拟使用的线程调度程序。
        /// </summary>
        public SimpleThreadDispatcher ThreadDispatcher { get; private set; }

        protected Demo()
        {
            BufferPool = new BufferPool();
            // 通常,在每个物理核心具有多个逻辑核心的系统上,将尽可能多的线程放入模拟中不会产生最佳结果。
            // Environment.ProcessorCount仅报告逻辑核心计数,因此我们在这里将使用一个简单的启发式方法-它将使一个或两个逻辑核心处于空闲状态。
            // 对于带有超线程的常见英特尔四核,这将使用六个逻辑核心,并留下两个逻辑核心用于其他用途。
            // 这绝不是完美的。为了最大限度地提高性能,您需要分析您的模拟和目标硬件。
            // 请注意,如果所有逻辑核心都被分配工作,则问题在Windows 7等较旧的操作系统上可能会放大。

            // 通常,相对于CPU计算吞吐量,内存带宽越大,相对于解算,模拟的冲突检测负担就越大。
            // 您从SMT/超线程中获得的好处就越多。
            // 例如,如果在由数千个布娃娃组成的场景中使用64核四通道内存通道AMD 3990x,
            // 将没有足够的内存带宽来填充一半的物理内核。使用全部128个逻辑核心只会增加开销。

            // 可能值得使用像hwloc这样的工具来提取额外的信息来进行推理。
            var targetThreadCount = Math.Max(1, Environment.ProcessorCount > 4 ? Environment.ProcessorCount - 2 : Environment.ProcessorCount - 1);
            ThreadDispatcher = new SimpleThreadDispatcher(targetThreadCount);
        }

        public virtual void LoadGraphicalContent(ContentArchive content, RenderSurface surface)
        {
        }

        public abstract void Initialize(ContentArchive content, Camera camera);


        public virtual void Update(Window window, Camera camera, Input input, float dt)
        {
            // 在演示中,我们每帧使用一个时间步长。我们不会费心修改不同监视器的物理时间步长,因此刷新率也会不同
            // 更改模拟速率。不过,这实际上不会改变模拟的结果,而且简单性非常适合演示。
            // 在"真实"应用程序的上下文中,您可以改为使用时间累加器来根据需要采用固定长度的时间步长,或者
            // 完全分离不同线程间的模拟和渲染速率。
            // (在这两种情况下,您还需要在渲染过程中插值或外推模拟结果以获得平滑度。)
            // 请注意,采取可变长度的步骤可能会降低稳定性。渐进的或一次性的改变可以相当好地发挥作用。
            Simulation.Timestep(1 / 60f);

            //// 下面是一个示例,说明使用更频繁的更新,但每次更新调用的模拟时间仍然是固定的：
            // Const Float Time ToSimulate=1/60F;
            // const int timestepsPerUpdate=2;
            // 常量浮点timePerTimestep=timeToSimulate/timestepsPerUpdate;
            // for(int i=0;i<timestepsPerUpdate;++i)
            // {
            // Simulation.Timestep(timePerTimestep,ThreadDispatcher);
            // }

            //// 下面是一个示例,说明如何使用累加器获取固定长度的多个时间步长,以响应变量UPDATE DT：
            // 时间累加器+=DT;
            // var targetTimestpDuration=1/120F;
            // WHILE(时间累加器>=targetTimestpDuration)
            // {
            // Simulation.Timestep(targetTimestpDuration,ThreadDispatcher);
            // 时间累加器-=targetTimestpDuration;
            // }
            //// 如果要平滑渲染对象的位置以避免每次更新的时间步数不可预测会导致的"抖动",请参见
            //// 您可以仅使用基于累加器中剩余时间的权重来插值先前和当前状态：
            // var interpolationWeight=时间累加器/targetTimestpDuration;
        }
        // 如果使用上面的基于累加器的时间步长方法,则需要此字段。
        // 浮动时间累加器;

        public virtual void Render(Renderer renderer, Camera camera, Input input, TextBuilder text, Font font)
        {
        }

        protected virtual void OnDispose()
        {

        }

        bool disposed;
        public void Dispose()
        {
            if (!disposed)
            {
                disposed = true;
                OnDispose();
                Simulation.Dispose();
                BufferPool.Clear();
                ThreadDispatcher.Dispose();
            }
        }

#if DEBUG
        ~Demo()
        {
            DemoRenderer.Helpers.CheckForUndisposed(disposed, this);
        }
#endif
    }
}
